import pandas as pd
import numpy as np
import csv
import math
import random
import matplotlib.pyplot as plt
from sklearn.metrics.pairwise import cosine_similarity
import torch
import torch.nn as nn
import time
import scipy.sparse as sps
from numpy.linalg import inv
class MV:
    def __init__(self,filePath,U,M,N,LENGTH,TEST):
        #filePath = "../data/drug_rat.csv"
        self.data = pd.read_csv(filePath)
        self.data = torch.tensor(self.data.values)
        self.U = U  # number of user用户数量
        self.M = M  # number of item电影数量
        self.COMMON_min = 2  # minimum number of common ratings used in constructing the network在网络建设中使用的最小公用评级数
        self.N = N  # k
        self.LENGTH = LENGTH#数据集大小
        self.TEST = TEST#测试集的大小
        self.b = torch.zeros(TEST, 1)  # the true ratings, after normalization归一化之后的评分
        self.F = np.empty((TEST), dtype=object)  # number of ratings x degree of filters examined. 分阶之后的评分
        for i in range(TEST):
            self.F[i] = np.zeros((6, 6))
        self.MU = torch.zeros(TEST, 1)

        self.b_cv = torch.zeros(LENGTH - TEST, 1)  # the true ratings, after normalization归一化之后的评分
        self.F_cv = np.empty((LENGTH - TEST), dtype=object)  # number of ratings x degree of filters examined. 分阶之后的评分
        for i in range(LENGTH - TEST):
            self.F_cv[i] = np.zeros((6, 6))
        self.MU_cv = torch.zeros(LENGTH - TEST, 1)
    def getSims(self):
        #torch.manual_seed(2)

        perm_idx = torch.arange(self.LENGTH)

        self.train_ALL_idx = perm_idx[0:self.LENGTH - self.TEST]

        self.r_train = torch.sparse.FloatTensor(
            torch.LongTensor(list([int(self.data[i, 0]), int(self.data[i, 1])] for i in self.train_ALL_idx)).t(),
            torch.FloatTensor(list(int(self.data[i, 2]) for i in self.train_ALL_idx)),
            torch.Size([self.U, self.M])).to_dense()
        self.r_train[self.r_train == 0] = float("NaN")
        self.mu_u = np.nanmean(self.r_train, axis=1)

        self.test_ALL_idx = perm_idx[self.LENGTH - self.TEST:]

        self.r_ALL_meanC_train = self.r_train - np.tile(self.mu_u, self.M).reshape(self.M, self.U).T
        self.r_ALL_meanC_train = torch.tensor(np.array(pd.DataFrame(np.array(self.r_ALL_meanC_train)).fillna(0).values))
        self.isRated = self.r_train.clone()
        self.isRated[self.isRated > 0] = 1
        self.isRated = torch.tensor(np.array(pd.DataFrame(np.array(self.isRated)).fillna(0).values))

        self.sims_u = torch.tensor(np.array(pd.DataFrame(self.r_train.T).astype('float32').corr(method='pearson').fillna(0)))
        temp = torch.mm(self.isRated, self.isRated.T)
        U_min_check_u = temp.clone()
        U_min_check_u[U_min_check_u < self.COMMON_min] = 1
        U_min_check_u[U_min_check_u >= self.COMMON_min] = 0
        U_min_check_u = U_min_check_u - torch.diag_embed(torch.diag(U_min_check_u))
        self.sims_u[U_min_check_u == 1] = 0

        self.sims_m = torch.tensor(np.array(pd.DataFrame(self.r_train).astype('float32').corr(method='pearson').fillna(0)))
        temp = torch.mm(self.isRated.T, self.isRated)
        U_min_check_m = temp.clone()
        U_min_check_m[U_min_check_m < self.COMMON_min] = 1
        U_min_check_m[U_min_check_m >= self.COMMON_min] = 0
        U_min_check_m = U_min_check_m - torch.diag_embed(torch.diag(U_min_check_m))
        self.sims_m[U_min_check_m == 1] = 0
    def getF(self):
        for i in range(len(self.test_ALL_idx)):
            u = self.data[self.test_ALL_idx[i], 0]
            m = self.data[self.test_ALL_idx[i], 1]

            A_u_crt = self.sims_u - torch.eye(self.U)
            A_u_crt[:, self.isRated[:, m] == 0] = 0  # 可能存在问题，待进一步验证
            temp = torch.sort(A_u_crt, dim=1, descending=True)[0]
            A_u_crt[A_u_crt < torch.tensor(np.tile(temp[:, self.N - 1], self.U).reshape(self.U, self.U).T)] = 0
            A_u_crt = A_u_crt / torch.tensor(np.tile(sum(A_u_crt.T), self.U).reshape(self.U, self.U).T)
            A_u_crt = torch.tensor(np.array(pd.DataFrame(np.array(A_u_crt)).fillna(0).values))

            A_m_crt = self.sims_m - torch.eye(self.M)
            A_m_crt[self.isRated[u, :] == 0, :] = 0  # 可能存在问题，待进一步验证
            temp = torch.sort(A_m_crt, dim=0, descending=True)[0]
            A_m_crt[A_m_crt < torch.tensor(np.tile(temp[self.N, :], self.M).reshape(self.M, self.M))] = 0
            A_m_crt = A_m_crt / torch.tensor(np.tile(sum(A_m_crt), self.M).reshape(self.M, self.M))
            A_m_crt = torch.tensor(np.array(pd.DataFrame(np.array(A_m_crt)).fillna(0).values))

            A_u_agg = torch.zeros(6, self.U)
            A_m_agg = torch.zeros(self.M, 6)

            A_u_agg[0, u] = 1
            temp = A_u_crt[u, :]
            A_u_agg[1, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[2, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[3, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[4, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[5, :] = temp

            A_m_agg[m, 0] = 1
            temp = A_m_crt[:, m]
            A_m_agg[:, 1] = temp
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 2] = temp.reshape(994)
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 3] = temp.reshape(994)
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 4] = temp.reshape(994)
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 5] = temp.reshape(994)

            self.F[i] = np.array(torch.mm(torch.mm(A_u_agg, self.r_ALL_meanC_train), A_m_agg))
            self.b[i] = self.data[self.test_ALL_idx[i], 2] - torch.tensor(self.mu_u[u])
            self.MU[i] = torch.tensor(self.mu_u[u])

            if i % 300 == 0 or i == len(self.test_ALL_idx) - 1:
                print("获取F,进度：{}%".format(round(i / (len(self.test_ALL_idx) - 1) * 100, 2)))
        self.b_clone = self.b.clone()
        self.MU_clone = self.MU.clone()
        self.F_clone = self.F.copy()
    def total_RMSE_new(self,a, F_crt, b, MU):
        b_hat = torch.mm(F_crt, a) + MU
        # print(b_hat)
        b_hat[b_hat > 5] = 5
        b_hat[b_hat < 1] = 1
        b_hat = b_hat - MU
        RMSE = 0
        for i in range(len(b)):
            RMSE += (b_hat[i] - b[i]) ** 2
        RMSE = RMSE / len(b)
        return float(RMSE[0]) ** 0.5
    def MAE(self,x, y, F, b, MU):
        b_hat = torch.zeros(self.TEST, 1)
        for i in range(self.TEST):
            # print(torch.mm(x.T,torch.tensor(F[i][0:len(x),0:len(y)])))
            # print(x.T.shape)
            # print(torch.tensor(F[i][0:len(x),0:len(y)]).shape)
            # print(y.shape)
            b_hat[i] = torch.mm(
                torch.mm(x.T.reshape(1, len(x)).double(), torch.tensor(F[i][0:len(x), 0:len(y)]).double()),
                y.double()) + MU[i]
        b_hat[b_hat > 5] = 5
        b_hat[b_hat < 1] = 1
        b_hat = b_hat - MU
        mae = 0
        for i in range(len(b)):
            mae += math.fabs(b_hat[i] - b[i])
        mae = mae / len(b)
        return mae
    def total_RMSE_xFy(self,x, y, F, b, MU):
        b_hat = torch.zeros(self.TEST, 1)
        for i in range(self.TEST):
            # print(torch.mm(x.T,torch.tensor(F[i][0:len(x),0:len(y)])))
            # print(x.T.shape)
            # print(torch.tensor(F[i][0:len(x),0:len(y)]).shape)
            # print(y.shape)
            b_hat[i] = torch.mm(
                torch.mm(x.T.reshape(1, len(x)).double(), torch.tensor(F[i][0:len(x), 0:len(y)]).double()),
                y.double()) + MU[i]
        b_hat[b_hat > 5] = 5
        b_hat[b_hat < 1] = 1
        b_hat = b_hat - MU
        RMSE = 0
        for i in range(len(b)):
            RMSE += (b_hat[i] - b[i]) ** 2
        RMSE = RMSE / len(b)
        return float(RMSE[0]) ** 0.5
    def getFUser(self):
        sims = torch.tensor(np.array(pd.DataFrame(self.r_train.T).astype('float32').corr(method='pearson').fillna(0)))
        temp = torch.mm(self.isRated, self.isRated.T)
        U_min_check = temp.clone()
        U_min_check[U_min_check < self.COMMON_min] = 1
        U_min_check[U_min_check >= self.COMMON_min] = 0
        U_min_check = U_min_check - torch.diag_embed(torch.diag(U_min_check))
        sims[U_min_check == 1] = 0

        self.b_user_cv = torch.zeros(self.LENGTH - self.TEST, 1)  # the true ratings, after normalization归一化之后的评分
        self.F_user_cv = torch.zeros(self.LENGTH - self.TEST, 6)  # number of ratings x degree of filters examined. 分阶之后的评分
        self.MU_user_cv = torch.zeros(self.LENGTH - self.TEST, 1)
        for i in range(len(self.train_ALL_idx)):
            u = self.data[self.train_ALL_idx[i], 0]
            m = self.data[self.train_ALL_idx[i], 1]
            A_crt = sims - torch.eye(self.U)
            A_crt[:, self.isRated[:, m] == 0] = 0  # 可能存在问题，待进一步验证
            temp = torch.sort(A_crt, dim=1, descending=True)[0]
            A_crt[A_crt < torch.tensor(np.tile(temp[:, self.N], self.U).reshape(self.U, self.U).T)] = 0
            A_crt = A_crt / torch.tensor(np.tile(sum(A_crt.T), self.U).reshape(self.U, self.U).T)
            A_crt = torch.tensor(np.array(pd.DataFrame(np.array(A_crt)).fillna(0).values))
            r_temp = self.r_ALL_meanC_train[:, m]
            self.b_user_cv[i] = self.data[self.train_ALL_idx[i], 2] - torch.tensor(self.mu_u[u])
            self.MU_user_cv[i] = torch.tensor(self.mu_u[u])
            temp = A_crt[u, :]
            self.F_user_cv[i, 0] = torch.mm(temp.float().reshape(1, self.U), r_temp.float().reshape(self.U, 1))
            temp = torch.mm(temp.float().reshape(1, self.U), A_crt.float())
            self.F_user_cv[i, 1] = torch.mm(temp.float().reshape(1, self.U), r_temp.float().reshape(self.U, 1))
            temp = torch.mm(temp.float().reshape(1, self.U), A_crt.float())
            self.F_user_cv[i, 2] = torch.mm(temp.float().reshape(1, self.U), r_temp.float().reshape(self.U, 1))
            temp = torch.mm(temp.float().reshape(1, self.U), A_crt.float())
            self.F_user_cv[i, 3] = torch.mm(temp.float().reshape(1, self.U), r_temp.float().reshape(self.U, 1))
            temp = torch.mm(temp.float().reshape(1, self.U), A_crt.float())
            self.F_user_cv[i, 4] = torch.mm(temp.float().reshape(1, self.U), r_temp.float().reshape(self.U, 1))
            temp = torch.mm(temp.float().reshape(1, self.U), A_crt.float())
            self.F_user_cv[i, 5] = torch.mm(temp.float().reshape(1, self.U), r_temp.float().reshape(self.U, 1))

            if i % 1000 == 0 or i == len(self.train_ALL_idx) - 1:
                print("获取Fu进度：{}%".format(round(i / (len(self.train_ALL_idx) - 1) * 100, 2)))
    def getF_cv(self):
        for i in range(len(self.train_ALL_idx)):
            u = self.data[self.train_ALL_idx[i], 0]
            m = self.data[self.train_ALL_idx[i], 1]

            A_u_crt = self.sims_u - torch.eye(self.U)
            A_u_crt[:, self.isRated[:, m] == 0] = 0  # 可能存在问题，待进一步验证
            temp = torch.sort(A_u_crt, dim=1, descending=True)[0]
            A_u_crt[A_u_crt < torch.tensor(np.tile(temp[:, self.N - 1], self.U).reshape(self.U, self.U).T)] = 0
            A_u_crt = A_u_crt / torch.tensor(np.tile(sum(A_u_crt.T), self.U).reshape(self.U, self.U).T)
            A_u_crt = torch.tensor(np.array(pd.DataFrame(np.array(A_u_crt)).fillna(0).values))

            A_m_crt = self.sims_m - torch.eye(self.M)
            A_m_crt[self.isRated[u, :] == 0, :] = 0  # 可能存在问题，待进一步验证
            temp = torch.sort(A_m_crt, dim=0, descending=True)[0]
            A_m_crt[A_m_crt < torch.tensor(np.tile(temp[self.N, :], self.M).reshape(self.M, self.M))] = 0
            A_m_crt = A_m_crt / torch.tensor(np.tile(sum(A_m_crt), self.M).reshape(self.M, self.M))
            A_m_crt = torch.tensor(np.array(pd.DataFrame(np.array(A_m_crt)).fillna(0).values))

            A_u_agg = torch.zeros(6, self.U)
            A_m_agg = torch.zeros(self.M, 6)

            A_u_agg[0, u] = 1
            temp = A_u_crt[u, :]
            A_u_agg[1, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[2, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[3, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[4, :] = temp
            temp = torch.mm(temp.reshape(1, self.U), A_u_crt)
            A_u_agg[5, :] = temp

            A_m_agg[m, 0] = 1
            temp = A_m_crt[:, m]
            A_m_agg[:, 1] = temp
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 2] = temp.reshape(994)
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 3] = temp.reshape(994)
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 4] = temp.reshape(994)
            temp = torch.mm(A_m_crt, temp.reshape(self.M, 1))
            A_m_agg[:, 5] = temp.reshape(994)

            self.F_cv[i] = np.array(torch.mm(torch.mm(A_u_agg, self.r_ALL_meanC_train), A_m_agg))
            self.F_cv[i][0,0] = 0
            self.b_cv[i] = self.data[self.train_ALL_idx[i], 2] - torch.tensor(self.mu_u[u])
            self.MU_cv[i] = torch.tensor(self.mu_u[u])

            if i % 3000 == 0 or i == len(self.train_ALL_idx) - 1:
                print("获取F_cv,进度：{}%".format(round(i / (len(self.train_ALL_idx) - 1) * 100, 2)))
            self.b_cv_clone = self.b_cv.clone()
            self.MU_cv_clone = self.MU_cv.clone()
            self.F_cv_clone = self.F_cv.copy()
    def getRMSE(self):
        rl = []
        ml = []
        result_all = torch.zeros(5, 5)
        for x_num in range(2, 7):
            for y_num in range(2, 7):
                C = 0
                F_cv_crt = self.F_user_cv[:, 0:x_num - 1]
                a_cv = torch.mm(torch.mm(torch.tensor(inv(torch.mm(F_cv_crt.T, F_cv_crt) +
                                                          C * torch.eye(x_num - 1))), F_cv_crt.T), self.b_user_cv)
                print(a_cv)
                a_start = torch.cat((torch.tensor([[0]]).double(), a_cv), 0)
                # a_start = torch.tensor(np.array([0,a_cv]))
                # print(a_start)
                F = self.F_clone.copy()
                MU = self.MU_clone.clone()
                b = self.b_clone.clone()

                for i in range(self.TEST):
                    F[i] = F[i][0:x_num, 0:y_num]
                C = 0
                T = 40
                x = a_start
                y = torch.zeros(y_num, 1)
                y[1] = 1
                result = torch.zeros(T + 1)
                result[0] = self.total_RMSE_xFy(x, y, F, b, MU)  ####################
                F_crt_y = torch.zeros(self.TEST, y_num)
                F_crt_x = torch.zeros(self.TEST, x_num)
                for t in range(T):
                    for i in range(self.TEST):
                        # print(F_crt_y[i,:].shape)
                        F_crt_y[i, :] = torch.mm(x.T.reshape(1, len(x)).double(), torch.tensor(F[i]).double())
                        # print(F_crt_y[i,:])
                    y = torch.mm(torch.mm(torch.tensor(inv(torch.mm(F_crt_y.T, F_crt_y) +
                                                           C * torch.eye(y_num))), F_crt_y.T), b)
                    for i in range(self.TEST):
                        # print(torch.tensor(F[i]).shape)
                        # print(y.shape)
                        # print(F_crt_x[i,:].shape)
                        F_crt_x[i, :] = torch.mm(torch.tensor(F[i]), y).reshape(x_num)
                    x = torch.mm(torch.mm(torch.tensor(inv(torch.mm(F_crt_x.T, F_crt_x) +
                                                           C * torch.eye(x_num))), F_crt_x.T), b)
                    result[t + 1] = self.total_RMSE_xFy(x, y, F, b, MU)  ####################
                result_all[x_num - 2, y_num - 2] = result[t + 1]
                print("test[{},{}]时，C={}情况下，RMSE为：{}".format(x_num - 1, y_num - 1, C, result[t + 1]))
                print("test[{},{}]时，C={}情况下，MAE为：{}".format(x_num - 1, y_num - 1, C,
                                                        self.MAE(x, y, F, b, MU)))
                rl.append(result[t + 1])
                ml.append(self.MAE(x, y, F, b, MU))
        return [torch.tensor(rl), torch.tensor(ml)]
    def getTrainRMSE(self):
        rl = []
        ml = []
        result_all = torch.zeros(5, 5)
        for x_num in range(2, 7):
            for y_num in range(2, 7):
                C = 0
                F_cv_crt = self.F_user_cv[:, 0:x_num - 1]
                a_cv = torch.mm(torch.mm(torch.tensor(inv(torch.mm(F_cv_crt.T, F_cv_crt) +
                                                          C * torch.eye(x_num - 1))), F_cv_crt.T), self.b_user_cv)
                # print(a_cv)
                a_start = torch.cat((torch.tensor([[0]]).double(), a_cv), 0)
                # a_start = torch.tensor(np.array([0,a_cv]))
                # print(a_start)
                F = self.F_clone.copy()
                MU = self.MU_clone.clone()
                b = self.b_clone.clone()

                F_cv = self.F_cv_clone.copy()
                MU_cv = self.MU_cv_clone.clone()
                b_cv = self.b_cv_clone.clone()

                for i in range(self.LENGTH - self.TEST):
                    F_cv[i] = F_cv[i][0:x_num, 0:y_num]
                for i in range(self.TEST):
                    F[i] = F[i][0:x_num, 0:y_num]
                C = 0
                T = 40
                x = a_start
                y = torch.zeros(y_num, 1)
                y[1] = 1
                result = torch.zeros(T + 1)
                result[0] = self.total_RMSE_xFy(x, y, F, b, MU)  ####################
                F_crt_y = torch.zeros(self.LENGTH - self.TEST, y_num)
                F_crt_x = torch.zeros(self.LENGTH - self.TEST, x_num)
                for t in range(T):
                    for i in range(self.LENGTH - self.TEST):
                        # print(F_crt_y[i,:].shape)
                        F_crt_y[i, :] = torch.mm(x.T.reshape(1, len(x)).double(), torch.tensor(F_cv[i]).double())
                        # print(F_crt_y[i,:])
                    y = torch.mm(torch.mm(torch.tensor(inv(torch.mm(F_crt_y.T, F_crt_y) +
                                                           C * torch.eye(y_num))), F_crt_y.T), b_cv)
                    for i in range(self.LENGTH - self.TEST):
                        # print(torch.tensor(F[i]).shape)
                        # print(y.shape)
                        # print(F_crt_x[i,:].shape)
                        F_crt_x[i, :] = torch.mm(torch.tensor(F_cv[i]), y).reshape(x_num)
                    x = torch.mm(torch.mm(torch.tensor(inv(torch.mm(F_crt_x.T, F_crt_x) +
                                                           C * torch.eye(x_num))), F_crt_x.T), b_cv)
                    result[t + 1] = self.total_RMSE_xFy(x, y, F, b, MU)  ####################
                result_all[x_num - 2, y_num - 2] = result[t + 1]
                print("train[{},{}]时，C={}情况下，RMSE为：{}".format(x_num - 1, y_num - 1, C, result[t + 1]))
                print("train[{},{}]时，C={}情况下，MAE为：{}".format(x_num - 1, y_num - 1, C,
                                                        self.MAE(x, y, F, b, MU)))
                rl.append(result[t + 1])
                ml.append(self.MAE(x, y, F, b, MU))
        return [torch.tensor(rl), torch.tensor(ml)]
if __name__ == '__main__':
    for i in range(10):
        filePath = "../data/drug_head/drug_head_" + str(i) + ".csv"
        movie = MV(filePath, 759, 994, 40, 37441, 450)
        RMSE = torch.zeros(1, 25)
        mae = torch.zeros(1, 25)
        RMSE1 = torch.zeros(1, 25)
        mae1 = torch.zeros(1, 25)
        movie.getSims()
        movie.getF()
        movie.getFUser()
        movie.getF_cv()
        gM1 = movie.getTrainRMSE()
        RMSE1 += gM1[0]
        mae1 += gM1[1]
        print("药物头部:{}/10平均RMSE为：\n{}".format(i + 1, RMSE1 / (i + 1)))
        print("药物头部:{}/10平均MAE为：\n{}".format(i + 1, mae1 / (i + 1)))
    for i in range(10):
        filePath = "../data/drug_tail/drug_tail_" + str(i) + ".csv"
        movie = MV(filePath, 759, 994, 40, 37441, 450)
        RMSE = torch.zeros(1, 25)
        mae = torch.zeros(1, 25)
        RMSE1 = torch.zeros(1, 25)
        mae1 = torch.zeros(1, 25)
        movie.getSims()
        movie.getF()
        movie.getFUser()
        movie.getF_cv()
        gM1 = movie.getTrainRMSE()
        RMSE1 += gM1[0]
        mae1 += gM1[1]
        print("药物头部:{}/10平均RMSE为：\n{}".format(i + 1, RMSE1 / (i + 1)))
        print("药物头部:{}/10平均MAE为：\n{}".format(i + 1, mae1 / (i + 1)))